﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MayaTime
{
	class MayaTime
	{
		const int longCountEpochJulianDayNumber = 584283;

		private int _longCountDays; // This is the AUTHORATIVE count, all other dates are created from it

		// Long Count:
		int _baktun = 0;
		int _katun = 0;
		int _tun = 0;
		int _winal = 0;
		int _kin = 0;

		// Haab:
		int _haabMonth = 0;
		int _haabDay = 0;

		// Tzolkin:
		int _tzolkinNumberedWeek = 1; // Tzolkin calendar counts from 1
		int _tzolkinNamedWeek = 0;

		public MayaTime(int gregorianDay, int gregorianMonth, int gregorianYear)
		{
			_longCountDays = 0;

			_longCountDays = JulianDayNumber(gregorianDay, gregorianMonth, gregorianYear) - longCountEpochJulianDayNumber;

			RefreshLongCountFields();
			RefreshHaabFields();
			RefreshTzolkinFields();
		}

		private int JulianDayNumber(int gregorianDay, int gregorianMonth, int gregorianYear)
		{
			// Julian Day Number starts Jan 1, 4713 BC and follows Julian date rules

			// Check: November 16, 1858 = 2,400,000 (Ref: http://en.wikipedia.org/wiki/Julian_day)
			// August 31, 2132 = 2,500,000

			// Below code taken from: http://en.wikipedia.org/wiki/Julian_day#Converting_Gregorian_calendar_date_to_Julian_Day_Number

			// Increment year to account for missing year 0
			if (gregorianYear < 0)
			{
				gregorianYear++;
			}

			double a = (14 - gregorianMonth) / 12;
			double y = gregorianYear + 4800 - a;
			double m = gregorianMonth + (12 * a) - 3;
			double jdn = gregorianDay + (((153 * m) + 2) / 5) + (365 * y) + (y / 4) - (y / 100) + (y / 400) - 32045;

			return (Convert.ToInt32(Math.Floor(jdn)));
		}

		private void RefreshLongCountFields()
		{
			_baktun = 0;
			_katun = 0;
			_tun = 0;
			_winal = 0;
			_kin = 0;

			for (int dayCount = 0; dayCount < _longCountDays; dayCount++)
			{
				_kin++;

				if (_kin > 19)
				{
					_kin = 0;
					_winal++;

					if (_winal > 17)
					{
						_winal = 0;
						_tun++;

						if (_tun > 19)
						{
							_tun = 0;
							_katun++;

							if (_katun > 19)
							{
								_katun = 0;
								_baktun++;
							}
						}
					}
				}
			}
		}

		private void RefreshHaabFields()
		{
			// long count epoch (13.0.0.0.0) = 8 Cumku

			int dayOfYear = (_longCountDays + 348) % 365;

			_haabMonth = dayOfYear / 20;
			_haabDay = dayOfYear % 20;
		}

		private void RefreshTzolkinFields()
		{
			// long count epoch (13.0.0.0.0) = 4 Ahau

			_tzolkinNumberedWeek = 1;
			_tzolkinNamedWeek = 0;

			int dayOfYear = (_longCountDays + 120) % 260;

			for (int i = 0; i < dayOfYear; i++)
			{
				_tzolkinNumberedWeek++;
				if (_tzolkinNumberedWeek > 13)
				{
					_tzolkinNumberedWeek = 1;
				}

				_tzolkinNamedWeek++;
				if (_tzolkinNamedWeek > 19)
				{
					_tzolkinNamedWeek = 0;
				}
			}
		}

		public string LongCountString()
		{
			int dispBaktun = _baktun;

			if (dispBaktun == 0)
			{
				dispBaktun = 13;
			}

			string MayaDateString = Convert.ToString(dispBaktun) + "."
									+ Convert.ToString(_katun) + "."
									+ Convert.ToString(_tun) + "."
									+ Convert.ToString(_winal) + "."
									+ Convert.ToString(_kin);

			return (MayaDateString);
		}

		public string HaabString()
		{
			// Month names from: http://www.webexhibits.org/calendars/calendar-mayan.html
			string monthName = null;

			switch (_haabMonth)
			{
				case 0:
					monthName = "Pop";
					break;
				case 1:
					monthName = "Uo";
					break;
				case 2:
					monthName = "Zip";
					break;
				case 3:
					monthName = "Zotz";
					break;
				case 4:
					monthName = "Tzec";
					break;
				case 5:
					monthName = "Xul";
					break;
				case 6:
					monthName = "Yaxkin";
					break;
				case 7:
					monthName = "Mol";
					break;
				case 8:
					monthName = "Chen";
					break;
				case 9:
					monthName = "Yax";
					break;
				case 10:
					monthName = "Zac";
					break;
				case 11:
					monthName = "Ceh";
					break;
				case 12:
					monthName = "Mac";
					break;
				case 13:
					monthName = "Kankin";
					break;
				case 14:
					monthName = "Muan";
					break;
				case 15:
					monthName = "Pax";
					break;
				case 16:
					monthName = "Kayab";
					break;
				case 17:
					monthName = "Cumku";
					break;
				case 18:
					monthName = "Uayeb";
					break;
			}

			return (Convert.ToString(_haabDay) + " " + monthName);
		}

		public string TzolkinString()
		{
			// Day names from: http://www.webexhibits.org/calendars/calendar-mayan.html

			string dayName = null;

			switch (_tzolkinNamedWeek)
			{
				case 0:
					dayName = "Ahau";
					break;
				case 1:
					dayName = "Imix";
					break;
				case 2:
					dayName = "Ik";
					break;
				case 3:
					dayName = "Akbal";
					break;
				case 4:
					dayName = "Kan";
					break;
				case 5:
					dayName = "Chicchan";
					break;
				case 6:
					dayName = "Cimi";
					break;
				case 7:
					dayName = "Manik";
					break;
				case 8:
					dayName = "Lamat";
					break;
				case 9:
					dayName = "Muluc";
					break;
				case 10:
					dayName = "Oc";
					break;
				case 11:
					dayName = "Chuen";
					break;
				case 12:
					dayName = "Eb";
					break;
				case 13:
					dayName = "Ben";
					break;
				case 14:
					dayName = "Ix";
					break;
				case 15:
					dayName = "Men";
					break;
				case 16:
					dayName = "Cib";
					break;
				case 17:
					dayName = "Caban";
					break;
				case 18:
					dayName = "Etznab";
					break;
				case 19:
					dayName = "Caunac";
					break;
			}

			return (Convert.ToString(_tzolkinNumberedWeek) + " " + dayName);
		}
	}
}
